/*
 * Copyright 2018-2023 guerlab.net and other contributors.
 *
 * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.gnu.org/licenses/lgpl-3.0.html
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.guerlab.sms.template.support.freemarker.loader;

import java.io.IOException;
import java.io.Reader;

import freemarker.cache.TemplateCache;

/**
 * 模板加载器.
 *
 * @param <T> 对象类型
 * @author guer
 */
public interface FreeMarkerTemplateLoader<T> {

	/**
	 * 协议分割符.
	 */
	String PROTOCOL_DIVISION = ":";

	/**
	 * 协议分割符长度.
	 */
	int PROTOCOL_DIVISION_LENGTH = PROTOCOL_DIVISION.length();

	/**
	 * 协议.
	 *
	 * @return 协议.
	 */
	String protocol();

	boolean accept(Object templateSource);

	/**
	 * Finds the template in the backing storage and returns an object that identifies the storage location where the
	 * template can be loaded from. See the return value for more information.
	 *
	 * @param name The name (template root directory relative path) of the template, already localized and normalized by
	 *             the {@link freemarker.cache.TemplateCache cache}. It is completely up to the loader implementation to
	 *             interpret the name, however it should expect to receive hierarchical paths where path components are
	 *             separated by a slash (not backslash). Backslashes (or any other OS specific separator character) are
	 *             not considered as separators by FreeMarker, and thus they will not be replaced with slash before
	 *             passing to this method, so it's up to the template loader to handle them (say, by throwing an
	 *             exception that tells the user that the path (s)he has entered is invalid, as (s)he must use slash --
	 *             typical mistake of Windows users). The passed names are always considered relative to some
	 *             loader-defined root location (often referred as the "template root directory"), and will never start
	 *             with a slash, nor will they contain a path component consisting of either a single or a double dot --
	 *             these are all resolved by the template cache before passing the name to the loader. As a side effect,
	 *             paths that trivially reach outside template root directory, such as <tt>../my.ftl</tt>, will be
	 *             rejected by the template cache, so they never reach the template loader. Note again, that if the path
	 *             uses backslash as path separator instead of slash as (the template loader should not accept that), the
	 *             normalization will not properly happen, as FreeMarker (the cache) recognizes only the slashes as
	 *             separators.
	 * @return An object representing the template source, which can be supplied in subsequent calls to
	 * {@link #getLastModified(Object)} and {@link #getReader(Object, String)}, when those are called on the
	 * same {@link freemarker.cache.TemplateLoader}. {@code null} must be returned if the source for the template doesn't exist;
	 * don't throw exception then! The exact type of this object is up to the {@link freemarker.cache.TemplateLoader}
	 * implementation. As this object is possibly used as hash key in caches, and is surly compared with another
	 * template source for equality, <b>it must have a proper {@link Object#equals(Object)} and
	 * {@link Object#hashCode()}) implementation</b>. Especially, template sources that refer to the same
	 * physical source must be equivalent, otherwise template caching can become inefficient. This is only
	 * expected from {@link Object#equals(Object)} when the compared template sources came from the same
	 * {@link freemarker.cache.TemplateLoader} instance. Also, it must not influence the equality if the source is open or
	 * closed ({@link #closeTemplateSource(Object)}).
	 * @throws IOException When an error occurs that makes it impossible to find out if the template exists, or to access the
	 *                     existing template. Don't throw exception if the template doesn't exist, instead return with
	 *                     {@code null} then!
	 */
	Object findTemplateSource(String name)
			throws IOException;

	/**
	 * Returns the time of last modification of the specified template source. This method is called after
	 * <code>findTemplateSource()</code>.
	 *
	 * @param templateSource an object representing a template source (the template file), obtained through a prior call to
	 *                       {@link #findTemplateSource(String)}. This must be an object on which
	 *                       {@link freemarker.cache.TemplateLoader#closeTemplateSource(Object)} wasn't applied yet.
	 * @return The time of last modification for the specified template source, or -1 if the time is not known. This
	 * value meant to be milliseconds since the epoch, but in fact FreeMarker doesn't care what it means, it
	 * only cares if it changes, in which case the template needs to be reloaded (even if the value has
	 * decreased). -1 is not special in that regard either; if you keep returning it, FreeMarker won't
	 * reload the template (as far as it's not evicted from the cache from some other
	 * reason). Note that {@link Long#MIN_VALUE} is reserved for internal use.
	 */
	long getLastModified(Object templateSource);

	/**
	 * Returns the character stream of a template represented by the specified template source. This method is possibly
	 * called for multiple times for the same template source object, and it must always return a {@link Reader} that
	 * reads the template from its beginning. Before this method is called for the second time (or later), its caller
	 * must close the previously returned {@link Reader}, and it must not use it anymore. That is, this method is not
	 * required to support multiple concurrent readers for the same source {@code templateSource} object.
	 *
	 * <p>
	 * Typically, this method is called if the template is missing from the cache, or if after calling
	 * {@link #findTemplateSource(String)} and {@link #getLastModified(Object)} it was determined that the cached copy
	 * of the template is stale. Then, if it turns out that the {@code encoding} parameter used doesn't match the actual
	 * template content (based on the {@code #ftl encoding=...} header), this method will be called for a second time
	 * with the correct {@code encoding} parameter value.
	 *
	 * <p>
	 * Unlike {@link #findTemplateSource(String)}, this method must not tolerate if the template is not found, and
	 * must throw {@link IOException} in that case.
	 *
	 * @param templateSource an object representing a template source, obtained through a prior call to
	 *                       {@link #findTemplateSource(String)}. This must be an object on which
	 *                       {@link freemarker.cache.TemplateLoader#closeTemplateSource(Object)} wasn't applied yet.
	 * @param encoding       the character encoding used to translate source bytes to characters. Some loaders may not have access
	 *                       to the byte representation of the template stream, and instead directly obtain a character stream.
	 *                       These loaders should ignore the encoding parameter.
	 * @return A {@link Reader} representing the template character stream; not {@code null}. It's the responsibility of
	 * the caller (which is {@link TemplateCache} usually) to {@code close()} it. The {@link Reader} is not
	 * required to work after the {@code templateSource} was closed ({@link #closeTemplateSource(Object)}).
	 * @throws IOException if an I/O error occurs while accessing the stream.
	 */
	Reader getReader(Object templateSource, String encoding) throws IOException;

	/**
	 * Closes the template source, releasing any resources held that are only required for reading the template and/or
	 * its metadata. This is the last method that is called by the {@link TemplateCache} for a template source, except
	 * that {@link Object#equals(Object)} is might called later too. {@link TemplateCache} ensures that this method will
	 * be called on every object that is returned from {@link #findTemplateSource(String)}.
	 *
	 * @param templateSource the template source that should be closed.
	 */
	void closeTemplateSource(Object templateSource) throws IOException;
}
